//----------------------------------------------------------
import processing.opengl.*;   // otherwise you get an error message!
import javax.media.opengl.*;
import java.awt.event.*;      // import for mouse wheel event handling
import mri.*;                 // import for displaying .3ds models

boolean debugKeys = false;    // show key values
boolean showWindowed = false;  // false: start fullscreen
boolean showHelp = false;     // true: show command keys
boolean loadSkybox = true;    // true: load skybox textures
int skyboxCount = 0;          // number of skyboxes

float PI=3.1416;
skyboxViewer skybox = new skyboxViewer();
int NUM_PISOS=30;
int NUM_TREES=3;
int NUM_NUBES=5;
V3dsScene[] pisos=new V3dsScene[NUM_PISOS];
V3dsScene[] helices=new V3dsScene[NUM_PISOS];
V3dsScene base;
V3dsScene[] trees=new V3dsScene[NUM_TREES];
int[] posTrees=new int[NUM_TREES*2];
Nube[] nubes=new Nube[NUM_NUBES];
float[] posNubes=new float[NUM_NUBES*3];
boolean dibNubes=false, dibTrees=false, dibCursor=true, luz1=true,luz2=true;
int numImagen=0;

//variables de transformaciones
float rotx;
float roty;
int radio=150;
float z;
float x;
float y;
float contador=0.0;
float contador2=0.0;
float separacion= 0.0;
int estadoSeparacion= 0;
int estadoGiro = 0;
float theta=0;

//Constantes 
float X_INI=0, Y_INI=20, Z_INI=150;
float ROTX_INI=-0.05459957, ROTY_INI=PI/4;

//----------------------------------------------------------
void setup()
{
  cursor(WAIT);
  //Decide cómo se desplegara el programa: Ventana/Fullscreen
  display(showWindowed); 
  x=X_INI;
  y=Y_INI;
  z=Z_INI;
  radio=(int)Z_INI;
  theta=0;
  rotx=ROTX_INI;
  roty=ROTY_INI;

  hint( ENABLE_OPENGL_4X_SMOOTH );
  smooth();

  //Inicialización de los objetos
  for (int i=0;i<NUM_PISOS;i++) {
    pisos[i] = new V3dsScene( this, "Piso3.3ds" );
    helices[i] = new V3dsScene( this, "turbina.3ds" );
  }
  base=new V3dsScene(this, "base.3ds");
  for (int i=0;i<NUM_TREES;i++) {
    trees[i]=new V3dsScene(this, "tree1.3ds");
    posTrees[i*2]=(int)random(-30, 30);
    posTrees[i*2+1]=(int)random(-30, 30);
  }
  for (int i=0;i<NUM_NUBES;i++) {
    nubes[i]=new Nube((int)random(10, 15));
    posNubes[i*3]=random(-100, 100);
    posNubes[i*3+1]=random(-75, -100);
    posNubes[i*3+2]=random(-100, 100);
  }

  background(0);
  frameRate(60);

  skyboxCount = skybox.getSkyboxFileNames();
  println("Skyboxcount: "+skyboxCount);
  loadSkybox = (skyboxCount > 0);

  frame.addMouseWheelListener(new MouseWheelInput());   // add mouse wheel listener
}
//----------------------------------------------------------
void draw()
{

 
  desplegaSkybox();

  perspective( PI*0.25, 4.0/3.0, 1, 5000 );
  //se pusieron las variables x,y, z para que pudieramos manejar la camara
  camera( x, y, z, 0, 0, 0, 0, 1, 0 );//cam_pos.x, cam_pos.y, cam_pos.z, cam_tar.x, cam_tar.y, cam_tar.z, cam_up.x, cam_up.y, cam_up.z );

  GL _gl = ((PGraphicsOpenGL)g).beginGL();

  if(!luz1 && !luz2)
    setupLight1( _gl, new float[] {0, 0, 0}, 1);
  if(luz1)
    setupLight1( _gl, new float[] {0, 20, -100}, 1);
  if(luz2)
    setupLight2( _gl, new float[] {0, 20, 100}, 1);
  ((PGraphicsOpenGL)g).endGL();
  

  pushMatrix();

  //rotaciones del edificio para poderlo ver desde distintos angulos
  rotateX(rotx);
  rotateY(roty);
  if (dibNubes)
    dibujaNubes();  
  //traslacion en y para bajar el edificio
  translate(0, -50, 0);
  if (dibTrees)
    dibujaTrees();
  pushMatrix();
  scale(7, 1, 7);
  float trans=6;
  translate(trans, 0, -trans);
  rotate(radians(180));
  base.draw();
  popMatrix();
  dibujaPisos(); 
  dibujaHelices();
  popMatrix();
  //usado para ir incrementando los giros
  contador += 0.001;
  contador2+=.1;
}

void dibujaNubes() {
  for (int i=0;i<NUM_NUBES;i++) {
    pushMatrix();
    translate(posNubes[i*3], posNubes[i*3+1], posNubes[i*3+2]);
    nubes[i].dibujaNube();
    popMatrix();
  }
}
void dibujaTrees() {

  for (int i=0;i<NUM_TREES;i++) {
    pushMatrix();
    scale(2, 2, 2);
    translate(posTrees[i*2], 0, posTrees[i*2+1]);
    rotate(radians(180));
    trees[i].draw();
    popMatrix();
  }
}
void dibujaPisos() {
  pushMatrix();
  rotateY(180);
  pushMatrix();
  for (int i=0; i<NUM_PISOS; i++) {

    scale(0.97, 1, 0.93);
    //separacion entre pisos
    translate(0, 3.2, 0);
    //giro de este piso
    if(estadoGiro == 0)
    rotateY(mouseX*0.0015-0.597);
    else
    rotateY(contador);
    
    // Codigo que se encarga de la traslacion suave de los pisos para 
    // poder visualizar las helices
    pushMatrix();
    if (i % 2 == 0) {
      if (estadoSeparacion == 1) {
        if (separacion >= 3) 
          estadoSeparacion = 2;
        else 
          separacion += 0.01;
      }
      if (estadoSeparacion == 3) {
        if (separacion <= 0.0) 
          estadoSeparacion = 0;
        else 
          separacion -= 0.01;
      }
      translate(0, separacion, 0);
    }
    if (estadoSeparacion == 2) {
      if (i % 2 == 0)
        pisos[i].draw();
    }
    else pisos[i].draw();
    popMatrix();
  }
  popMatrix();
  popMatrix();
}

// Dibuja las helices que se encuentran entre los pisos
void dibujaHelices() {
  pushMatrix();
  translate(0, 2.3, 0);
  pushMatrix();
  for (int i=0; i<NUM_PISOS-1; i++) {
    scale(0.95, 1, 0.95);
    translate(0, 3, 0);
    rotateY(-contador);
    pushMatrix();
    // Seccion de codigo que se encarga de la separacion de las helices
    // de un modo suave
    if (i % 2 == 0) {
      if (estadoSeparacion == 1) {
        if (separacion >= 3) estadoSeparacion = 2;
        else separacion += 0.01;
      }
      if (estadoSeparacion == 3) {
        if (separacion <= 0.0) estadoSeparacion = 0;
        else separacion -= 0.01;
      }
      translate(0, separacion, 0);
    }
    if (estadoSeparacion == 2) {
      if (i % 2 == 0)
        helices[i].draw();
    }
    else helices[i].draw();

    popMatrix();
  }
  popMatrix();
  popMatrix();
}

// Metodo que ayuda a desplegar la ventana o a mostrarla como fullscreen

void display(boolean isWindowed) {
  if (isWindowed) { 
    size(800, 600, OPENGL);
    centerWindow();
  }
  else {              //  start fullscreen
    size(screenWidth, screenHeight, OPENGL); 
    frame.setLocation(-3, -32);
  }
}


// Carga las imágenes correspondientes para desplegar el skybox

void desplegaSkybox() {
  if (skyboxCount == 0)
  {
    String errorMsg = "Sorry, no skybox image files found at '"+skyboxesDir+"'";
    println(errorMsg);
    showText(errorMsg, 22, 66);
    noLoop();
    return;
  }

  if (loadSkybox)    // load next skybox ?
  {
    skybox.loadCurrentSkybox();
    loadSkybox = false;
  }

  skybox.drawCube();
  skybox.setRotationSpeed (0.0);
}

// val is 0 or 1. 0 = directional light, 1 = point light

// Configura las variables necesarias para desplegar las luces

void setupLight1( GL g, float[] pos, float val)
{
  float[] light_emissive = { 
    0.0f, 0.0f, 0.0f, 1
  };
  float[] light_ambient = { 
    0.0f, 0.0f, 0.0f, 1
  };
  float[] light_diffuse = { 
    1.0f, 1.0f, 1.0f, 1.0f
  };
  float[] light_specular = { 
    1.0f, 1.0f, 1.0f, 1.0f
  };  
  float[] light_position = { 
    pos[0], pos[1], pos[2], val
  };  

  g.glLightfv ( GL.GL_LIGHT1, GL.GL_AMBIENT, light_ambient, 0 );
  g.glLightfv ( GL.GL_LIGHT1, GL.GL_DIFFUSE, light_diffuse, 0 );
  g.glLightfv ( GL.GL_LIGHT1, GL.GL_SPECULAR, light_specular, 0 );
  g.glLightfv ( GL.GL_LIGHT1, GL.GL_POSITION, light_position, 0 );  
  g.glEnable( GL.GL_LIGHT1 );
  g.glEnable( GL.GL_LIGHTING );

  //g.glEnable( GL.GL_COLOR_MATERIAL );
}
void setupLight2( GL g, float[] pos, float val)
{
  float[] light_emissive = { 
    0.0f, 0.0f, 0.0f, 1
  };
  float[] light_ambient = { 
    0.0f, 0.0f, 0.0f, 1
  };
  float[] light_diffuse = { 
    1.0f, 1.0f, 1.0f, 1.0f
  };
  float[] light_specular = { 
    1.0f, 1.0f, 1.0f, 1.0f
  };  
  float[] light_position = { 
    pos[0], pos[1], pos[2], val
  };  

  g.glLightfv ( GL.GL_LIGHT2, GL.GL_AMBIENT, light_ambient, 0 );
  g.glLightfv ( GL.GL_LIGHT2, GL.GL_DIFFUSE, light_diffuse, 0 );
  g.glLightfv ( GL.GL_LIGHT2, GL.GL_SPECULAR, light_specular, 0 );
  g.glLightfv ( GL.GL_LIGHT2, GL.GL_POSITION, light_position, 0 );  
  g.glEnable( GL.GL_LIGHT2 );
  g.glEnable( GL.GL_LIGHTING );

  //g.glEnable( GL.GL_COLOR_MATERIAL );
}



//----------------------------------------------------------
// show 2d help information
//----------------------------------------------------------
void showText(String infoText, int x, int y)
{
  perspective();
  fill(255);
  text(infoText, x, y);
}
// Centra la ventana de la aplicación/animación en el centro de la pantalla
// donde se está desplegando
void centerWindow()
{
  frame.setLocation((screenWidth - width) / 2, (screenHeight - height) / 4);
}

//=== event handling ===

//----------------------------------------------------------
// handle mouse input
//----------------------------------------------------------
void mouseDragged()
{
  // Control del giro del edificio sobre el eje 'x' y 'y'.
  float rate = 0.01;
  rotx += (pmouseY-mouseY) * rate;
  roty += (mouseX-pmouseX) * rate;
}
//----------------------------------------------------------
// listen for MouseWheelEvent
// Función donde se obtiene la información del scroll del mouse
//----------------------------------------------------------
class MouseWheelInput implements MouseWheelListener
{ 
  // Se incremente/decrementa el radio sobre el cual está
  // localizada la cámara y se calculan las nuevas 
  // coordenadas de ella
  void mouseWheelMoved(MouseWheelEvent e)
  { 
    if(radio >= 50 && radio <= 249)
    radio=radio + e.getWheelRotation();
    if ( radio > 50 && radio < 54 )
    radio ++;
    if ( radio > 245 && radio < 250 )
    radio --;
    z=radio*cos(theta);
    x=radio*sin(theta);
    println("\n" + radio);
  }
}

//----------------------------------------------------------
// handle key input
//----------------------------------------------------------
void keyPressed()
{

  if (key == CODED) {
    // La cámara con direccion a Y positivo
    if (keyCode == UP) {
      y =y+1;
      skybox.addRotationX (-0.01);
    }
    // La cámara con direccion a Y negativo
    else if (keyCode == DOWN) {
      if(y>Y_INI){
        y =y-1;
        skybox.addRotationX (0.01);
      }
    }
    // Tanto las teclas de izquierda como de derecha desplazan la cámara
    // alrededor de un círculo imaginario cuyo centro se encuentra en el 
    // edifcio y cuyo radio está dado.
    else if (keyCode == LEFT){ 
      theta+=0.01;
      z=radio*cos(theta);
      x=radio*sin(theta);
    }
    else if (keyCode == RIGHT) {
      theta-=0.01;
      z=radio*cos(theta);
      x=radio*sin(theta);
    }
  }
  // Crea la separación de los pisos para visualizar las hélices
  if (key == 'a')
    estadoSeparacion = 1;
  // Crea la aparición de los pisos desplazados para que el edificio regrese
  // a su estado natural
  else if (key == 'A')
    estadoSeparacion = 3;
  // Reinicia la animación y la regresa a su estado inicial
  else if (key == 'r'){
    contador=0;
    x=X_INI;
    y=Y_INI;
    z=Z_INI;
    rotx=ROTX_INI;
    roty=ROTY_INI;
    radio=(int)Z_INI;
    theta=0;
    skybox.resetRotation();
  }
  // Dibuja/Oculta las nubes
  else if (key=='n')
    dibNubes=(!dibNubes);
  // // Dibuja/Oculta los árboles
  else if (key =='t')
    dibTrees=(!dibTrees);
  // Guarda en formato JPEG una vista actual de la escena
  else if(key=='s')
    save("Vista"+(numImagen++)+".jpg");
  // Animación automática del edificio
  else if (key =='g'){
    estadoGiro = 1;
    contador = 0;
  }
  // Animación manual del edificio
  else if (key =='G'){
    estadoGiro = 0;
    contador = 0;
  }
  // Despliega/Oculta el puntero
  else if(key=='c'){
    dibCursor=(!dibCursor);
    if(dibCursor)
      cursor(CROSS);
    else
      noCursor();
  }
  // Alterna las imágenes del skybox entre día y noche
  else if(key=='b')
    skybox.loadNextSkybox();
  // Enciende/Apaga la primera fuente de luz
  else if(key=='1')
    luz1=(!luz1);
  // Enciende/Apaga la segunda fuente de luz
  else if(key=='2')
    luz2=(!luz2);
}

